/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

#pragma once

#include <array>
#include "zen_number.h"
#include "zen_point.h"

// export
namespace Zen
{
    template <typename TPoint, size_t N>
    class BezierBase;

    using Bezier2Dx3 = BezierBase<Point2, 3>;
    using Bezier2Dx4 = BezierBase<Point2, 4>;
    using Bezier3Dx3 = BezierBase<Point3, 3>;
    using Bezier3Dx4 = BezierBase<Point3, 4>;
} // namespace Zen

namespace Zen
{
    inline Point2 Lerp(ConstPoint2& vStart, ConstPoint2& vEnd, float t)
    {
        return Point2Lerp(vStart, vEnd, t);
    }
    inline Point3 Lerp(ConstPoint3& vStart, ConstPoint3& vEnd, float t)
    {
        return Point3Lerp(vStart, vEnd, t);
    }
    inline Point4 Lerp(ConstPoint4& vStart, ConstPoint4& vEnd, float t)
    {
        return Point4Lerp(vStart, vEnd, t);
    }

    template <typename Tangent, typename TPoint, size_t N>
    class __BezierCalc;

    template <typename Tangent, typename TPoint>
    class __BezierCalc<Tangent, TPoint, 2>
    {
      public:
        static Tangent GetTangent(std::array<TPoint, 2> const& ctrls, float value)
        {
            return Tangent{ ctrls.at(0), ctrls.at(1) };
        }
    };

    template <typename Tangent, typename TPoint>
    class __BezierCalc<Tangent, TPoint, 3>
    {
      public:
        static Tangent GetTangent(std::array<TPoint, 3> const& ctrls, float value)
        {
            auto v1 = Lerp(ctrls.at(0), ctrls.at(1), value);
            auto v2 = Lerp(ctrls.at(1), ctrls.at(2), value);
            return Tangent{ v1, v2 };
        }
    };

    template <typename Tangent, typename TPoint>
    class __BezierCalc<Tangent, TPoint, 4>
    {
      public:
        static Tangent GetTangent(std::array<TPoint, 4> const& ctrls, float value)
        {
            auto v1 = Lerp(ctrls.at(0), ctrls.at(1), value);
            auto v2 = Lerp(ctrls.at(1), ctrls.at(2), value);
            auto v3 = Lerp(ctrls.at(2), ctrls.at(3), value);
            return { Lerp(v1, v2, value), Lerp(v2, v3, value) };
        }
    };

    template <typename Tangent, typename TPoint, size_t N>
    class __BezierCalc
    {
        static_assert(N > 4);

      public:
        static Tangent GetTangent(std::array<TPoint, N> const& ctrls, float value)
        {
            std::array<TPoint, N - 1> t;
            for (int i = 0; i < N - 1; ++i)
            {
                t[i] = Lerp(ctrls.at(i), ctrls.at(i + 1), value);
            }
            for (int e = N - 2; e > 1; --e)
            {
                for (int i = 0; i < e; ++i)
                {
                    t[i] = Lerp(t[i], t[i + 1], value);
                }
            }
            return { t[0], t[1] };
        }
    };

    template <typename TPoint, size_t N>
    class BezierBase
    {
      public:
        using Point = TPoint;
        static const size_t ControlCount = N;

        struct Tangent
        {
            TPoint startPoint;
            TPoint endPoint;
        };

        BezierBase() = default;

        void SetControl(size_t index, TPoint point)
        {
            mControls.at(index) = point;
        }
        TPoint const& GetControl(size_t index)
        {
            return mControls.at(index);
        }

        Tangent GetTangent(float value)
        {
            return __BezierCalc<Tangent, TPoint, N>::GetTangent(mControls, value);
        }
        TPoint GetPoint(float value)
        {
            auto tangent = __BezierCalc<Tangent, TPoint, N>::GetTangent(mControls, value);
            return Lerp(tangent.startPoint, tangent.endPoint, value);
        }

      private:
        std::array<TPoint, N> mControls{};
    };

} // namespace Zen
